package com.superyee.commonlib.http;

import android.content.Context;
import android.text.TextUtils;


import com.superyee.commonlib.constants.AppConfig;
import com.superyee.commonlib.utils.AppLog;

import org.apache.http.conn.ConnectTimeoutException;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.FileNameMap;
import java.net.SocketTimeoutException;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;


public class OkHttpClientManager {

    private static volatile OkHttpClientManager mInstance;
    private OkHttpClient mOkHttpClient;

    private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");

    private OkHttpClientManager(Context context) {
//        File cacheFile = new File(BaseApplication.getAppContext().getExternalCacheDir(), "cache");
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(25, TimeUnit.SECONDS)
//                .cache(new Cache(cacheFile, 1024 * 1024 * 10))
                .addInterceptor(new HeaderParamsInterceptor(context.getApplicationContext()));
        if (AppConfig.DEBUG) {
            builder.addInterceptor(new LoggingInterceptor());
        }
//                .addInterceptor(new CacheInterceptor())
//                .addNetworkInterceptor(new CacheInterceptor())
        mOkHttpClient = builder.addInterceptor(new TokenAuthInterceptor(context.getApplicationContext()))
                .build();
        //cookie enabled
//        mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
//        mOkHttpClient.setConnectTimeout(HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS);
//        mDelivery = new Handler(Looper.getMainLooper());
    }


    public Response execute(Request request) throws IOException {
        return mOkHttpClient.newCall(request).execute();
    }

    public static OkHttpClientManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (OkHttpClientManager.class) {
                if (mInstance == null) {
                    mInstance = new OkHttpClientManager(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 同步的Get请求
     *
     * @param url
     * @return Response
     */
    private Response _getAsyn(String url) throws IOException {
        final Request request = new Request.Builder()
                .url(url)
                .build();
        Call call = mOkHttpClient.newCall(request);
        Response execute = call.execute();
        return execute;
    }

    /**
     * 同步的Get请求
     *
     * @param url
     * @return 字符串
     */
    private String _getAsString(String url) throws IOException {
        Response execute = _getAsyn(url);
        String result = execute.body().string();
        try {
            execute.body().close();
        } catch (Exception e) {
        }
        return result;
    }


    /**
     * 异步的get请求
     *
     * @param url
     * @param callback
     */
    private void _getAsyn(String url, final ResultCallback callback) {
        final Request request = new Request.Builder()
                .url(url)
                .build();
        deliveryResult(callback, request);
    }


    /**
     * 同步的Post请求
     *
     * @param url
     * @param params post的参数
     * @return
     */
    private Response _post(String url, List<Param> params) throws IOException {
        Request request = buildPostRequest(url, params);
        Response response = mOkHttpClient.newCall(request).execute();
        return response;
    }


    /**
     * 同步的Post请求
     *
     * @param url
     * @param params post的参数
     * @return 字符串
     */
    private String _postAsString(String url, List<Param> params) throws IOException {
        Response response = _post(url, params);
        String result = response.body().string();
        try {
            response.body().close();
        } catch (Exception e) {
        }
        return result;
    }

    /**
     * 异步的post请求
     *
     * @param url
     * @param callback
     * @param params
     */
    private void _postAsyn(String url, final ResultCallback callback, List<Param> params) {
        Request request = buildPostRequest(url, params);
        deliveryResult(callback, request);
    }


    /**
     * 异步的post请求
     *
     * @param url
     * @param callback
     * @param params
     */
    private void _postAsyn(String url, final ResultCallback callback, Map<String, String> params) {
        List<Param> paramsArr = map2Params(params);
        Request request = buildPostRequest(url, paramsArr);
        deliveryResult(callback, request);
    }


    /**
     * 同步基于post的文件上传
     *
     * @param params
     * @return
     */
    private Response _post(String url, List<File> files, List<String> fileKeys, List<Param> params) throws IOException {
        RequestBody requestBody = buildMultipartFormRequest(files, fileKeys, params, null);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        return mOkHttpClient.newCall(request).execute();
    }

    private Response _post(String url, List<File> file, List<String> fileKey) throws IOException {
        RequestBody requestBody = buildMultipartFormRequest(file, fileKey, null, null);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        return mOkHttpClient.newCall(request).execute();
    }


    /**
     * 异步基于post的文件上传，文件不带参数上传
     *
     * @param url
     * @param callback
     * @param fileList
     * @param fileKey
     * @throws IOException
     */
    private void _postAsyn(String url, ResultCallback callback, List<File> fileList, List<String> fileKey) throws IOException {
        RequestBody requestBody = buildMultipartFormRequest(fileList, fileKey, null, null);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        deliveryResult(callback, request);
    }

    private Request buildDeleteRequest(String url, List<Param> params) {
        params = validateParam(params);
        FormBody.Builder builder = new FormBody.Builder();

        for (Param param : params) {
            if (param.value != null)
                builder.add(param.key, param.value);
        }
        RequestBody requestBody = builder.build();
        return new Request.Builder()
                .url(url).delete(requestBody)
                .build();
    }

    /**
     * 异步基于post的文件上传，多文件且携带其他form参数上传
     *
     * @param url
     * @param callback
     * @param file
     * @param fileKey
     * @param params
     * @throws IOException
     */
    private void _postAsyn(String url, ResultCallback callback, List<File> file, List<String> fileKey, List<Param> params, String contentType) throws IOException {
        RequestBody requestBody = buildMultipartFormRequest(file, fileKey, params, contentType);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        deliveryResult(callback, request);
    }

    /**
     * 异步基于patch的文件上传，多文件且携带其他form参数上传
     *
     * @param url
     * @param callback
     * @param file
     * @param fileKey
     * @param params
     * @throws IOException
     */
    private void _patchAsyn(String url, ResultCallback callback, List<File> file, List<String> fileKey, List<Param> params, String contentType) throws IOException {
        RequestBody requestBody = buildMultipartFormRequest(file, fileKey, params, contentType);
        Request request = new Request.Builder()
                .url(url)
                .patch(requestBody)
                .build();
        deliveryResult(callback, request);
    }

    /**
     * 异步基于put的form参数提交
     *
     * @param url
     * @param callback
     * @param params
     * @throws IOException
     */
    private void _putAsyn(String url, ResultCallback callback, List<Param> params) throws IOException {
        RequestBody requestBody = buildForm(params);
        Request request = new Request.Builder()
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .addHeader("Accept", "*/*")
                .put(requestBody)
                .build();
        deliveryResult(callback, request);
    }

    /**
     * 异步下载文件
     *
     * @param url
     * @param path     本地文件存储的文件夹
     * @param fileName 本地文件存储文件名
     * @param callback
     */
    private void _downloadAsyn(final String url, final String path, final String fileName, final FileCallback callback) {
        final Request request = new Request.Builder()
                .url(url)
                .build();
        final Call call = mOkHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (callback != null) {
                    callback.onDownloadFailed(e.getMessage());
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.code() != 200) {
                    String msg = response.body().string();
                    response.body().close();
                    if (callback != null) {
                        callback.onDownloadFailed(msg);
                    }
                    return;
                }
                //删除本地文件
                if (!TextUtils.isEmpty(fileName)) {
                    File localFile = new File(path, fileName);
                    if (localFile != null && localFile.exists()) {
                        localFile.delete();
                    }
                }

                InputStream is = null;
                byte[] buf = new byte[1024];
                FileOutputStream fos = null;
                // 储存下载文件的目录
                File pathFile = new File(path);
                if (!pathFile.exists()) {
                    pathFile.mkdirs();
                }
                try {
                    String temName = UUID.randomUUID().toString();
                    final File temFile = new File(path, temName);
                    if (temFile.exists()) {
                        temFile.delete();
                    }
                    try {
                        temFile.createNewFile();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    fos = new FileOutputStream(temFile);
                    long sum = 0, total = response.body().contentLength();
                    int len = 0;
                    is = response.body().byteStream();
                    long start = System.currentTimeMillis();
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                        sum += len;
                        if (callback != null) {
                            if ((System.currentTimeMillis() - start) >= 1000) {
                                start = System.currentTimeMillis();
                                callback.onDownloading(sum, total);
                            }
                        }
                    }
                    fos.flush();
                    File newFile = new File(path, fileName);
                    if (newFile.exists()) {
                        newFile.delete();
                    }
                    temFile.renameTo(newFile);
                    if (callback != null) {
                        callback.onSuccess(newFile);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    if (callback != null) {
                        String error = null;
                        if (e != null && (e instanceof SocketTimeoutException || SocketTimeoutException.class.equals(e.getCause()) || e instanceof ConnectTimeoutException) || e instanceof UnknownHostException
                                || e instanceof ConnectException) {
                            error = AppConfig.CONNECTTIMEOUT;
                        } else {
                            error = e.getMessage();
                        }
                        callback.onDownloadFailed(error);
                        AppLog.printD("httpdownalod", e.getMessage());
                    }
                } finally {
                    try {
                        if (is != null)
                            is.close();
                    } catch (IOException e) {
                    }
                    try {
                        if (fos != null)
                            fos.close();
                    } catch (IOException e) {
                    }
                }
            }
        });
    }

    private String getFileNameFromHeader(Headers header) {
        String disposition = header.get("Content-Disposition");
        if (!TextUtils.isEmpty(disposition)) {
            boolean encode = disposition.contains("filename*=");
            String[] string = disposition.split(";");
            if (header != null && string.length > 0) {
                for (String str : string) {
                    if (encode) {
                        if (str.contains("filename*=")) {
                            String[] arr = str.split("=");
                            String name = arr[1];
                            if (!TextUtils.isEmpty(name)) {
                                name = name.replace("\"", "");
                                arr = name.split("''");
                                if (arr != null && arr.length == 2) {
                                    try {
                                        return URLDecoder.decode(arr[1], arr[0]);
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    } else {
                        if (str.contains("filename=")) {
                            String[] arr = str.split("=");
                            String name = arr[1];
                            if (!TextUtils.isEmpty(name)) {
                                return name.replace("\"", "");
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private String getFileName(String path) {
        int separatorIndex = path.lastIndexOf("/");
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }


    //*************对外公布的方法************


    public static Response getAsyn(Context context, String url) throws IOException {
        return getInstance(context)._getAsyn(url);
    }

    public static HttpResponse getSyn(Context context, String url) throws IOException {
        Response response = getInstance(context)._getAsyn(url);
        String resultStr = response.body().string();
        HttpResponse result = new HttpResponse();
        try {
            response.body().close();
        } catch (Exception e) {
        }
        result.setResult(resultStr);
        result.setResultCode(response.code());
        return result;
    }


    public static String getAsString(Context context, String url) throws IOException {
        return getInstance(context)._getAsString(url);
    }

    public static void getAsyn(Context context, String url, ResultCallback callback) {
        getInstance(context)._getAsyn(url, callback);
    }

    public static Response post(Context context, String url, List<Param> params) throws IOException {
        return getInstance(context)._post(url, params);
    }

    public static String postAsString(Context context, String url, List<Param> params) throws IOException {
        return getInstance(context)._postAsString(url, params);
    }

    /**
     * 同步请求
     *
     * @param context
     * @param url
     * @param params
     * @return
     * @throws IOException
     */
    public static HttpResponse postSyn(Context context, String url, List<File> files, List<String> fileKeys, List<Param> params) throws IOException {
        Response response = getInstance(context)._post(url, files, fileKeys, params);
        String resultStr = response.body().string();
        HttpResponse result = new HttpResponse();
        try {
            response.body().close();
        } catch (Exception e) {
        }
        result.setResult(resultStr);
        result.setResultCode(response.code());
        return result;
    }

    public static void postAsyn(Context context, String url, final ResultCallback callback, List<Param> params) {
        getInstance(context)._postAsyn(url, callback, params);
    }


    public static void postAsyn(Context context, String url, final ResultCallback callback, Map<String, String> params) {
        getInstance(context)._postAsyn(url, callback, params);
    }


    public static Response post(Context context, String url, List<File> files, List<String> fileKeys, List<Param> params) throws IOException {
        return getInstance(context)._post(url, files, fileKeys, params);
    }

    public static Response post(Context context, String url, List<File> file, List<String> fileKey) throws IOException {
        return getInstance(context)._post(url, file, fileKey);
    }


    public static void postAsyn(Context context, String url, ResultCallback callback, List<File> file, List<String> fileKey) throws IOException {
        getInstance(context)._postAsyn(url, callback, file, fileKey);
    }

    public static void delete(Context context, String url, List<Param> params, ResultCallback callback) {
        Request request = getInstance(context).buildDeleteRequest(url, params);
        getInstance(context).deliveryResult(callback, request);
    }

    public static void postAsyn(Context context, String url, ResultCallback callback, List<File> file, List<String> fileKey, List<Param> params, String contentType) throws IOException {
        getInstance(context)._postAsyn(url, callback, file, fileKey, params, contentType);
    }

    public static void patchAsyn(Context context, String url, ResultCallback callback, List<File> file, List<String> fileKey, List<Param> params, String contentType) throws IOException {
        getInstance(context)._patchAsyn(url, callback, file, fileKey, params, contentType);
    }

    public static void putAsyn(Context context, String url, ResultCallback callback, List<File> file, List<String> fileKey, List<Param> params, String contentType) throws IOException {
        getInstance(context)._putAsyn(url, callback, params);
    }

    public static void postJsonAsyn(Context context, String url, String json, ResultCallback callback) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
        Request request = new Request.Builder().url(url).post(body).build();
        getInstance(context).deliveryResult(callback, request);
    }

    public static void postForm(Context context, String url, HttpParams params, ResultCallback callback) {
        FormBody.Builder builder = new FormBody.Builder();
        if (params != null && params.getParams() != null) {
            for (Param param : params.getParams()) {
                builder.add(param.key, param.value);
            }
        }
        Request request = new Request.Builder().addHeader("Content-Type", "x-www-form-urlencoded").url(url).post(builder.build())
                .addHeader("Accept", "*/*")
                .build();
        getInstance(context).deliveryResult(callback, request);
    }


    public static void deleteJson(Context context, String url, String json, ResultCallback callback) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
        Request request = new Request.Builder().url(url).delete(body).build();
        getInstance(context).deliveryResult(callback, request);
    }

    public static void putJson(Context context, String url, String json, ResultCallback callback) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
        Request request = new Request.Builder().url(url).put(body).build();
        getInstance(context).deliveryResult(callback, request);
    }

    /**
     * @param context
     * @param url      下载地址
     * @param destDir  保存路径
     * @param fileName 保存文件名
     * @param callback
     */
    public static void downloadAsyn(Context context, String url, String destDir, String fileName, FileCallback callback) {
        getInstance(context)._downloadAsyn(url, destDir, fileName, callback);
    }

    //****************************

    private RequestBody buildForm(List<Param> params) {
        FormBody.Builder builder = new FormBody.Builder();
        if (params != null) {
            for (Param param : params) {
                builder.add(param.key, param.value);
            }
        }
        return builder.build();
    }

    private Request buildPostRequest(String url, List<Param> params) {
        if (params == null) {
            params = new ArrayList<>(0);
        }
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        for (Param param : params) {
            if (param.value != null)
                builder.addFormDataPart(param.key, param.value);
        }
        RequestBody requestBody = builder.build();
        return new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
    }

    private RequestBody buildMultipartFormRequest(List<File> files,
                                                  List<String> fileKeys, List<Param> params, String contentType) {
        params = validateParam(params);
        if (files == null || files.isEmpty()) {
            return buildForm(params);
        }
        MediaType type = null;
        if (TextUtils.isEmpty(contentType)) {
            type = MultipartBody.FORM;
        } else {
            type = MediaType.parse(contentType);
        }
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(type);

        for (Param param : params) {
            if (param.value != null)
                builder.addFormDataPart(param.key, param.value);
        }
        if (files != null) {
            RequestBody fileBody = null;
            for (int i = 0; i < files.size(); i++) {
                File file = files.get(i);
                if (file == null) {
                    break;
                }
                String fileName = file.getName();
                fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
                try {
                    fileName = URLEncoder.encode(fileName, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                builder.addFormDataPart(fileKeys.get(i), fileName, fileBody);
//                builder.addPart(Headers.of("Content-Disposition",
//                        "form-data; name=\"" + fileKeys.get(i) + "\"; filename=\"" + fileName + "\""),
//                        fileBody);
            }
        }
        return builder.build();
    }


    private String guessMimeType(String path) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }


    private List<Param> validateParam(List<Param> params) {
        if (params == null)
            return new ArrayList<Param>(0);
        else return params;
    }

    private List<Param> map2Params(Map<String, String> params) {
        if (params == null) return new ArrayList<>(0);
        int size = params.size();
        List<Param> res = new ArrayList<>(size);
        Set<Map.Entry<String, String>> entries = params.entrySet();
        int i = 0;
        for (Map.Entry<String, String> entry : entries) {
            res.add(new Param(entry.getKey(), entry.getValue()));
        }
        return res;
    }

    private static final String SESSION_KEY = "Set-Cookie";
    private static final String mSessionKey = "JSESSIONID";

    private Map<String, String> mSessions = new HashMap<String, String>();

    private void deliveryResult(final ResultCallback callback, Request request) {
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                sendFailedStringCallback(call, e, callback);
            }


            @Override
            public void onResponse(Call call, final Response response) {
                try {
                    final String string = response.body().string();
                    try {
                        response.body().close();
                    } catch (Exception e) {
                    }
                    sendSuccessResultCallback(response.code(), string, callback);
                } catch (Exception e) {
                    sendFailedStringCallback(call, e, callback);
                }
            }
        });
    }

    private void sendFailedStringCallback(final Call request, final Exception e, final ResultCallback callback) {
//        mDelivery.post(new Runnable()
//        {
//            @Override
//            public void run()
//            {
        if (callback != null)
            callback.onError(request, e);
//            }
//        });
    }

    private void sendSuccessResultCallback(int code, final String object, final ResultCallback callback) {
//        mDelivery.post(new Runnable()
//        {
//            @Override
//            public void run()
//            {
        if (callback != null) {
            callback.onResponse(code, object);
        }
//            }
//        });
    }


    public static abstract class ResultCallback {
//        public Type mType;

//        public ResultCallback() {
//            mType = getSuperclassTypeParameter(getClass());
//        }
//
//        static Type getSuperclassTypeParameter(Class<?> subclass) {
//            Type superclass = subclass.getGenericSuperclass();
//            if (superclass instanceof Class) {
//                throw new RuntimeException("Missing type parameter.");
//            }
//            ParameterizedType parameterized = (ParameterizedType) superclass;
//            return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
//        }

        public abstract void onError(Call request, Exception e);

        public abstract void onResponse(int statusCode, String response);
    }

    public static abstract class FileCallback {
        public abstract void onSuccess(File file);

        public abstract void onDownloading(long progress, long total);

        public abstract void onDownloadFailed(String message);
    }

    public static class Param {
        public Param() {
        }

        public Param(String key, String value) {
            this.key = key;
            this.value = value;
        }

        String key;
        String value;
    }


}
